#ifndef MATRIX_H
#define MATRIX_H

#define OUTOFRANGE 1
#define INVALIDSIZE 2
#define INVALIDOPERATION 3
#define CHILDMATRIX 4
#define NOTIMPLEMENTED 5
#define UNSUPPORTEDSIZE 6

#include <memory>
#include "base_iterator.h"


template <typename T> 
class Matrix 
{ 
private: 
    std::shared_ptr<T> items;
    int rows;
    int columns;
    int itemsCount;
    int rowOffset;
    int columnOffset;
    Matrix<T> *parent;

    int CalculateIndex(int N, int M) const;
    T GetMinorForElement(int N, int M) const;
    Matrix<T> GetComplementsMatrix() const;

    template<bool B>
    base_iterator<B, T> begin_internal() const
    {
        if(parent == NULL)
            return base_iterator<B, T>(items.get());

        int offset = CalculateIndex(0, 0);
        return base_iterator<B, T>(items.get() + offset, rows, columns, parent->rows, parent->columns, rowOffset, columnOffset, offset);
    }

    template<bool B>
    base_iterator<B, T> end_internal() const
    {
        if(parent == NULL)
            return base_iterator<B, T>(items.get() + itemsCount);

        int offset = CalculateIndex(rows, 0);
        return base_iterator<B, T>(items.get() + offset, rows, columns, parent->rows, parent->columns, rowOffset, columnOffset, offset);
    };

public:
    Matrix(int N);
    Matrix(int N, int M);
    Matrix(Matrix<T> &parent, int N, int M);
    Matrix(Matrix<T> &parent, int rowOffset, int columnOffset, int N, int M);
    ~Matrix(void);

    void Initialize(T number);
    void Initialize(int min, int max);
    void Initialize(T* collection);
    void Zeros();
    void Ones();
    void Eyes();

    Matrix<T> Clone() const;

    template<typename T2> friend class Matrix;

    template <typename T2>
    Matrix<T2> Convert() const;

    void Show(void);
    int GetRowsCount() const;
    int GetColumnsCount() const;
    T ElementAt(int N, int M) const;
    void SetElementAt(int N, int M, T value);
    Matrix<T> Transpose() const;
    Matrix<double> Inverse() const;
    T CalculateDeterminant() const;

    T Min();
    T Max();

    Matrix<T>& operator=(Matrix<T>& otherMatrix);
    bool operator==(Matrix<T>& otherMatrix);
    Matrix<T> operator+(Matrix<T>& otherMatrix);
    Matrix<T> operator-(Matrix<T>& otherMatrix);
    Matrix<T> operator*(Matrix<T>& otherMatrix);
    Matrix<T> operator/(Matrix<T>& otherMatrix);
    Matrix<T>& operator+=(Matrix<T>& otherMatrix);
    Matrix<T>& operator-=(Matrix<T>& otherMatrix);
    Matrix<T>& operator*=(Matrix<T>& otherMatrix);
    Matrix<T>& operator/=(Matrix<T>& otherMatrix);

    typedef base_iterator<false, T> iterator;
    typedef base_iterator<true, T> const_iterator;

    iterator begin()
    {
        return begin_internal<false>();
    };

    iterator end()
    {
        return end_internal<false>();
    };

    const_iterator begin() const
    {
        return begin_internal<true>();
    };

    const_iterator end() const
    {
        return end_internal<true>();
    };
};

template <typename T>
Matrix<T>& operator+=(Matrix<T>& matrix, T value);

template <typename T> 
Matrix<T>& operator-=(Matrix<T>& matrix, T value);

template <typename T>
Matrix<T>& operator*=(Matrix<T>& matrix, T value);

template <typename T> 
Matrix<T>& operator/=(Matrix<T>& matrix, T value);

template <typename T>
Matrix<T> operator+(Matrix<T>& matrix, T value);

template <typename T>
Matrix<T> operator-(Matrix<T>& matrix, T value);

template <typename T>
Matrix<T> operator*(Matrix<T>& matrix, T value);

template <typename T>
Matrix<T> operator/(Matrix<T>& matrix, T value);

template <typename T>
Matrix<T> operator*(T value, Matrix<T>& matrix);

template <typename T>
Matrix<T> operator+(T value, Matrix<T>& matrix);

template <typename T>
std::ostream& operator<<(std::ostream& output, const Matrix<T>& matrix);

template <typename T> 
Matrix<T> MatrixMultiplication(Matrix<T>& A, Matrix<T>& B);

template <typename T>
Matrix<T> ResolveEquation(Matrix<T>& A, Matrix<T>& X);

#endif